using System;
using System.Collections.Generic;

namespace Magiq.Support
{
    public static class Reflection
    {
        private static readonly ReflectionBuilder Builder = new ReflectionBuilder();

        private static readonly IDictionary<string, Func<object, object>> FieldGetters =
            new Dictionary<string, Func<object, object>>();

        private static readonly IDictionary<string, Func<object, object>> PropertyGetters =
            new Dictionary<string, Func<object, object>>();

        private static readonly IDictionary<string, Action<object, object>> PropertySetters =
            new Dictionary<string, Action<object, object>>();

        public static T Field<T>(this object self, string field)
        {
            return (T)Field(self, field);
        }

        private static string Key(Type type, string member)
        {
            return type.FullName + "." + member;
        }

        public static object Field(this object self, string field)
        {
            var fields = field.Split('.');
            var value = self;

            for (var i = 0; i < fields.Length; i++)
            {
                var currentField = fields[i];

                var key = Key(value.GetType(), currentField);
                Func<object, object> getter;

                if (!FieldGetters.TryGetValue(key, out getter))
                {
                    getter = Builder.FieldGetter(value.GetType(), currentField);
                    FieldGetters.Add(key, getter);
                }
                value = getter(value);
            }
            return value;
        }

        public static object Property(this object self, string property)
        {
            var value = self;
            var properties = property.Split('.');

            for (var i = 0; i < properties.Length; i++)
            {
                var key = Key(value.GetType(), property);
                Func<object, object> getter;

                if (!PropertyGetters.TryGetValue(key, out getter))
                {
                    getter = Builder.PropertyGetter(value.GetType(), property);
                    PropertyGetters.Add(key, getter);
                }
                value = getter(value);
            }
            return value;
        }

        public static T Property<T>(this object self, string property)
        {
            return (T)self.Property(property);
        }

        public static void SetProperty(this object self, string property, object value)
        {
            self.GetType().PropertySetter(property)(self, value);
        }

        public static Action<object, object> PropertySetter(this Type self, string property)
        {
            Action<object, object> setter;
            var key = Key(self, property);
            if (!PropertySetters.TryGetValue(key, out setter))
            {
                setter = Builder.PropertySetter(self, property);
                PropertySetters.Add(key, setter);
            }
            return setter;
        }

        public static bool HasProperty(this object self, string property)
        {
            return self.GetType().GetProperty(property) != null;
        }

    }
}